package mrHunter.server;

import mrHunter.ProblemInf;
import mrHunter.lib.Graph;
import mrHunter.lib.MyRandom;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.DecimalFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MRServer {
    private ProblemInf problemInf;

    public MRServer(){}

    public MRServer(ProblemInf problemInf){this.problemInf=problemInf;}

    private void MRconcat(String[] mr) throws IOException {
        String template="template"+problemInf.getNo();
        //String tempInput="C:\\Users\\yz_yang\\Documents\\mrhunter_v2\\mrWeb\\src\\main\\resources\\templates\\"+problemInf.getType()+"\\"+template;
        //String outputpath="C:\\Users\\yz_yang\\Documents\\mrhunter_v2\\mrWeb\\src\\main\\java\\mrHunter\\server\\MR.java";
        String root =  System.getProperty("evan.webapp");
        System.out.println("*************Here is the root path**************");
        System.out.println(root);
        root=root.substring(0,root.indexOf("target"));

        String tempInput=root+"\\src\\main\\resources\\templates\\"+problemInf.getType()+"\\"+template;
        String outputpath=root+"\\src\\main\\java\\mrHunter\\server\\MR.java";



        BufferedReader tempIn=new BufferedReader(new FileReader(tempInput));
        BufferedWriter out=new BufferedWriter(new FileWriter(outputpath));
        String tempLine=tempIn.readLine();

        //TODO:MR assert to return
        /*change assert to return in mr2*/
        String REGEX="assert\\(.*\\)";
        String INPUT=mr[1];
        String REPLACE;

        Pattern p=Pattern.compile(REGEX);
        Matcher m=p.matcher(INPUT);
        StringBuffer sb=new StringBuffer();
        while (m.find()){
            String temp=m.group(0);
            System.out.println(temp);
            REPLACE="return "+temp.substring(7,temp.length()-1);
            System.out.println("REPLACE");
            System.out.println(REPLACE);
            m.appendReplacement(sb,REPLACE);
        }
        m.appendTail(sb);
        mr[1]=sb.toString();

        /*concat mrs to templates*/
        for(int i=0;i<2;i++)
        {
            while(tempLine.charAt(0)!='%')
            {
                out.write(tempLine);
                out.newLine();
                tempLine=tempIn.readLine();
            }
            tempLine=tempIn.readLine();
            out.write(mr[i]);
            out.newLine();
        }

        while(tempLine!=null)
        {
            out.write(tempLine);
            out.newLine();
            tempLine=tempIn.readLine();
        }
        tempIn.close();
        out.close();
    }

    public String mrServer(String[] mr,int sourceInputNum,int followupInputNum) throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        //concat MR
        MRconcat(mr);

        String root =  System.getProperty("evan.webapp");
        root=root.substring(0,root.indexOf("target"));

        //compiling
        //File file = new File("C:\\Users\\yz_yang\\Documents\\mrhunter_v2\\mrWeb\\target\\classes\\mrHunter\\server\\MR.class");
        File file = new File(root+"\\target\\classes\\mrHunter\\server\\MR.class");
        file.deleteOnExit();
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //String distDir="C:\\Users\\yz_yang\\Documents\\mrhunter_v2\\mrWeb\\target\\classes\\";
        String distDir=root+"\\target\\classes\\";
        int compileResult = compiler.run(null,null, null,"-d",distDir,
                "-cp",distDir,
                root+"\\src\\main\\java\\mrHunter\\server\\MR.java");
        if(0 != compileResult){
            System.err.println("compile failed!");
            return "compile failed";
        }

        boolean res;
        URL url = new URL("file:"+distDir);
        URLClassLoader urlClass=new URLClassLoader(new URL[]{url});
        //URLClassLoader urlClass = new URLClassLoader(new URL[] {url}, this.getClass().getClassLoader());
        Class MR = urlClass.loadClass("mrHunter.server.MR");
        Class<?>[] paramTypes = new Class[]{String[].class,int.class,int.class};
        Method mrMethod=MR.getDeclaredMethod("userMR", paramTypes);

        String[] inputs=new String[3];
        MyRandom myRand=new MyRandom();

        int no=problemInf.getNo();
        String type=problemInf.getType();

        for(int i=0;i<1000;i++){
            for(int j=0;j<sourceInputNum;j++){
                //get random inputs
                inputs[j]=getInput(no,type);
            }
            res=(boolean)mrMethod.invoke(MR.newInstance(),inputs,sourceInputNum,followupInputNum);
            if(!res) {
                return failMessage(inputs,sourceInputNum);
            }
        }
        return "You passed!";
    }

    private String failMessage(String[] inputs,int sourceInputNum){
        String s="You failed at input ";
        switch (sourceInputNum){
            case 3:s+=inputs[2]+", ";
            case 2:s+=inputs[1]+", ";
            case 1:s+=inputs[0];
            default:break;
        }
        return s;
    }

    private String getInput(int no,String type){
        MyRandom myRand=new MyRandom();
        //TODO: ADD More Puzzles
        if(type.equals("math")){
            switch (no){
                case 1:
                case 2:{
                    //mySin
                    //myCos
                    DecimalFormat df = new DecimalFormat("#.000");
                    return df.format(myRand.randomDouble(-50.0,50.0));
                }
                case 3:{
                    //candyEating
                    int n=myRand.randomInt(1,50);
                    return String.valueOf(n);
                }
                case 4:{
                    //numOfPrime
                    return String.valueOf(myRand.randomInt(3,5000));
                }
                case 5:{
                    //hexConvert
                    return String.valueOf(myRand.randomInt(-1024,1024));
                }
                case 6:{
                    // TODO: new problem: triangleArea
                    return myRand.randomTriangle();
                }
                default:return "";
            }
        }
        else if(type.equals("string")){
            switch (no){
                case 1:{
                    //stringReverse
                    return myRand.randomString();
                }
                case 2:{
                    //wordReplacement
                    String s=myRand.randomString(5,50);
                    String s1=myRand.randomString(1,2);
                    String s2=myRand.randomString(1,5);
                    return s+","+s1+","+s2;
                }
                case 3:{
                    //stringAppend
                    String s0=myRand.randomString(2,5);
                    String s1=myRand.randomString(2,7);
                    return s0+","+s1;
                }
                default:return "";
            }
        }
        else if(type.equals("array")){
            switch (no){
                case 1:{
                    //binarySearch
                    int[] A=myRand.randomIntArray(2,2,-20,20);
                    int n=A.length;
                    int x=myRand.randomInt(-20,20);
                    StringBuilder res=new StringBuilder();
                    for(int i=0;i<n;i++){
                        res.append(A[i]);
                        if(i!=n-1)
                            res.append(" ");
                    }
                    res.append(",");
                    res.append(n).append(",").append(x);
                    return res.toString();
                }
                case 2:
                case 3:
                case 4: {
                    //maxSumOfContinuousArray
                    //myort
                    //inverseNumber
                    int[] x=myRand.randomIntArray(1,1);
                    StringBuilder res=new StringBuilder();
                    for(int i=0;i<x.length;i++){
                        res.append(x[i]);
                        if(i!=x.length-1)
                            res.append(" ");
                    }
                    return res.toString();
                }
                case 5:{
                    //mergeArray
                    int[] A=myRand.randomIntArray(2,1);
                    int[] B=myRand.randomIntArray(2,1);
                    StringBuilder input=new StringBuilder();
                    for(int j=0;j<A.length;j++){
                        input.append(A[j]);
                        if(j!=A.length-1)
                            input.append(" ");
                    }
                    input.append(",");
                    for(int j=0;j<B.length;j++){
                        input.append(B[j]);
                        if(j!=B.length-1)
                            input.append(" ");
                    }
                    return input.toString();
                }
                case 6:{
                    //kthOccurrence
                    int n=myRand.randomInt(1,50);
                    int[]A=new int[n];
                    for(int i=0;i<n;i++){
                        A[i]=myRand.randomInt(0,10);
                    }
                    int x=myRand.randomInt(0,10);
                    int k=myRand.randomInt(1,7);
                    StringBuilder res=new StringBuilder();
                    for(int i=0;i<n-1;i++){
                        res.append(A[i]).append(" ");
                    }
                    res.append(A[n-1]);
                    res.append(",").append(x).append(",").append(k);
                    return res.toString();
                }
                case 7:{
                    //Matrix Transpose
                    int M=myRand.randomInt(1,10);
                    int N=myRand.randomInt(1,10);
                    int[][] matrix=myRand.randomMatrix(M,N,-20,20);
                    StringBuilder res=new StringBuilder();
                    for(int i=0;i<M;i++){
                        for(int j=0;j<N;j++){
                            res.append(matrix[i][j]);
                            if(j!=N-1)
                                res.append(" ");
                        }
                        if(i!=M-1)
                            res.append("%");
                    }
                    return res.toString();
                }
                default:return "";
            }

        }
        else if(type.equals("graph")){
            switch (no){
                case 1:
                case 3: {
                    //connectedGraph
                    //getHamiltonCircuit
                    Graph G=myRand.randomGraph();
                    int v=G.getVertexNum();
                    int[][] edges=G.getEdges();
                    StringBuilder res=new StringBuilder();
                    for (int i=0;i<v;i++){
                        for(int j=0;j<v;j++){
                            res.append(edges[i][j]);
                            if(j!=v-1)
                                res.append(" ");
                        }
                        if(i!=v-1)
                            res.append("%");
                    }
                    return res.toString();
                }
                case 2:{
                    //shortestPath
                    Graph gInput;
                    do {
                        gInput = myRand.randomConnectedGraph();
                    } while (gInput.getVertexNum() <= 1);
                    int[][] edges = gInput.getEdges();
                    int vNum = gInput.getVertexNum();

                    StringBuilder input = new StringBuilder();
                    for (int j = 0; j < vNum; j++) {
                        for (int k = 0; k < vNum - 1; k++) {
                            input.append(edges[j][k]).append(" ");
                        }
                        input.append(edges[j][vNum - 1]);
                        if (j != vNum - 1) {
                            input.append("%");
                        }
                    }

                    int startV = myRand.randomInt(0, vNum);
                    int endV;
                    do {
                        endV = myRand.randomInt(0, vNum);
                    } while (endV == startV);
                    input.append(",").append(startV).append(",").append(endV);
                    return input.toString();
                }
                default:return "";
            }

        }
        else{
            return "";
        }
    }

    public String getOutput(String problemName,String problemType,String problemNo, String input) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Class x=Class.forName("mrHunter.puzzles."+problemType+".Puzzle"+problemNo);
        Method xMethod=x.getMethod(problemName,String.class);
        return (String)xMethod.invoke(x.newInstance(),input);
    }
}